﻿using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;

namespace CXW.Core
{
    /// <remark/>
    public class AppMutex : IDisposable
    {
        /// <summary>类<see cref="AppMutex"/>的只读静态实例</summary>
        public static readonly AppMutex Instance = new AppMutex();
        private IntPtr _mapping_handle = IntPtr.Zero;

        const string FILE_MAPPING_NAME = "{4161ecd2-374f-e202-c737-a93626cde209}";

        static byte[] _data = new byte[] {
            0x01, 0x02, 0x03, 0x04, 0x05,
            0x31, 0x32, 0x33, 0x34, 0x35,
            0x21, 0x22, 0x23, 0x24, 0x25,
            0x11, 0x12, 0x13, 0x14, 0x15
        };

        const int BUFFER_SIZE = 0x100;

        #region FILE_MAPPING_FLAG

        const int PAGE_NOACCESS = 0x01;
        const int PAGE_READONLY = 0x02;
        const int PAGE_READWRITE = 0x04;
        const int PAGE_WRITECOPY = 0x08;
        const int PAGE_EXECUTE = 0x10;
        const int PAGE_EXECUTE_READ = 0x20;
        const int PAGE_EXECUTE_READWRITE = 0x40;
        const int PAGE_EXECUTE_WRITECOPY = 0x80;
        const int PAGE_GUARD = 0x100;
        const int PAGE_NOCACHE = 0x200;
        const int PAGE_WRITECOMBINE = 0x400;

        const int STANDARD_RIGHTS_REQUIRED = 0x000F0000;
        const int SECTION_QUERY = 0x0001;
        const int SECTION_MAP_WRITE = 0x0002;
        const int SECTION_MAP_READ = 0x0004;
        const int SECTION_MAP_EXECUTE = 0x0008;
        const int SECTION_EXTEND_SIZE = 0x0010;

        const int SECTION_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SECTION_QUERY | SECTION_MAP_WRITE | SECTION_MAP_READ | SECTION_MAP_EXECUTE | SECTION_EXTEND_SIZE;

        const int FILE_MAP_COPY = SECTION_QUERY;
        const int FILE_MAP_WRITE = SECTION_MAP_WRITE;
        const int FILE_MAP_READ = SECTION_MAP_READ;
        const int FILE_MAP_ALL_ACCESS = SECTION_ALL_ACCESS;

        #endregion

        static void Check(bool condition)
        {
            if (!condition)
            {
                ThrowLastError();
            }
        }

        /// <remark/>
        public void Run()
        {
            _mapping_handle = CreateFileMapping(IntPtr.Zero, IntPtr.Zero, PAGE_READWRITE, 0, BUFFER_SIZE, FILE_MAPPING_NAME);
            Check(_mapping_handle != IntPtr.Zero);
            var view = MapViewOfFile(_mapping_handle, FILE_MAP_ALL_ACCESS, 0, 0, BUFFER_SIZE);
            Check(view != IntPtr.Zero);
            var data = PrepareInfo();
            Marshal.Copy(data, 0, view, Math.Min(BUFFER_SIZE, data.Length));
            Check(UnmapViewOfFile(view));
        }

        /// <remark/>
        public bool IsRunning()
        {
#if DEBUG

#else
            _mapping_handle = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, FILE_MAPPING_NAME);
            if (_mapping_handle != IntPtr.Zero)
            {
                var view = MapViewOfFile(_mapping_handle, FILE_MAP_ALL_ACCESS, 0, 0, BUFFER_SIZE);
                var buffer = new byte[BUFFER_SIZE];
                Marshal.Copy(view, buffer, 0, BUFFER_SIZE);
                Check(UnmapViewOfFile(view));

                try
                {
                    int pid = ProcessInfo(buffer);

                    System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById(pid);
                    var hwnd = p.MainWindowHandle;
                    if (ShowWindow(hwnd, 5))
                    {
                        SetForegroundWindow(hwnd);
                    }
                }
                catch (Exception e)
                {
                    LoggingService.Error("Bring process Exception", e);
                }

                return true;
            }
#endif
            return false;
        }

        int ProcessInfo(byte[] buffer)
        {
            int processId = BitConverter.ToInt32(buffer, 0);

            return processId;
        }

        byte[] PrepareInfo()
        {
            int processId = GetCurrentProcessId();

            var data = BitConverter.GetBytes(processId);
            return data;
        }

        bool Comparer(byte[] data, byte[] buffer)
        {
            if (data == null || buffer == null) return false;
            var length = Math.Min(data.Length, buffer.Length);

            for (int i = 0; i < length; i++)
            {
                if (buffer[i] != data[i])
                {
                    return false;
                }
            }
            return true;
        }

        static void ThrowLastError()
        {
            throw new SystemException("");
        }

        /// <remark/>
        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern int GetCurrentProcessId();

        /// <remark/>
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern IntPtr CreateFileMapping(IntPtr hFile, IntPtr lpAttributes, uint fProtect, uint dwMaximumSizeHigh, uint dwMaximumSizeLow, string lpName);

        /// <remark/>
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern IntPtr OpenFileMapping(uint dwDesiredAccess, bool bInheritHandle, string lpName);

        /// <remark/>
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        public static extern IntPtr MapViewOfFile(IntPtr handle, uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, uint dwNumerOfBytesToMap);

        /// <remark/>
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport("kernel32.dll", ExactSpelling = true)]
        public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);

        /// <remark/>
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool CloseHandle(IntPtr handle);

        /// <remark/>
        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        /// <remark/>
        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int flags);

        /// <remark/>
        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern bool SetForegroundWindow(IntPtr hWnd);

        /// <remark/>
        public void Dispose()
        {
            CloseHandle(_mapping_handle);
        }
    }
}
